+use std::fmt;
+use std::rc::Rc;
+use std::str::FromStr;
+
use semver::VersionReq;
use rustc_serialize::{Encoder, Encodable};
use core::{SourceId, Summary, PackageId};
-use std::rc::Rc;
-use util::CargoResult;
+use util::{CargoError, CargoResult, Cfg, CfgExpr, ChainError, human};
+
+/// Information about a dependency requested by a Cargo manifest.
+/// Cheap to copy.
+#[derive(PartialEq, Clone ,Debug)]
+pub struct Dependency {
+ inner: Rc<DependencyInner>,
+}
/// The data underlying a Dependency.
-#[derive(PartialEq,Clone,Debug)]
+#[derive(PartialEq, Clone, Debug)]
pub struct DependencyInner {
name: String,
source_id: SourceId,
// This dependency should be used only for this platform.
// `None` means *all platforms*.
- only_for_platform: Option<String>,
+ platform: Option<Platform>,
}
-/// Information about a dependency requested by a Cargo manifest.
-/// Cheap to copy.
-#[derive(PartialEq,Clone,Debug)]
-pub struct Dependency {
- inner: Rc<DependencyInner>,
+#[derive(Clone, Debug, PartialEq)]
+pub enum Platform {
+ Name(String),
+ Cfg(CfgExpr),
}
-
#[derive(RustcEncodable)]
struct SerializedDependency<'a> {
name: &'a str,
optional: bool,
uses_default_features: bool,
features: &'a [String],
- target: &'a Option<&'a str>,
+ target: Option<&'a Platform>,
}
impl Encodable for Dependency {
optional: self.is_optional(),
uses_default_features: self.uses_default_features(),
features: self.features(),
- target: &self.only_for_platform(),
+ target: self.platform(),
}.encode(s)
}
}
features: Vec::new(),
default_features: true,
specified_req: None,
- only_for_platform: None,
+ platform: None,
}
}
self.specified_req.as_ref().map(|s| &s[..])
}
- /// If none, this dependencies must be built for all platforms.
- /// If some, it must only be built for the specified platform.
- pub fn only_for_platform(&self) -> Option<&str> {
- self.only_for_platform.as_ref().map(|s| &s[..])
+ /// If none, this dependency must be built for all platforms.
+ /// If some, it must only be built for matching platforms.
+ pub fn platform(&self) -> Option<&Platform> {
+ self.platform.as_ref()
}
pub fn set_kind(mut self, kind: Kind) -> DependencyInner {
self
}
- pub fn set_only_for_platform(mut self, platform: Option<String>)
- -> DependencyInner {
- self.only_for_platform = platform;
+ pub fn set_platform(mut self, platform: Option<Platform>)
+ -> DependencyInner {
+ self.platform = platform;
self
}
/// If none, this dependencies must be built for all platforms.
/// If some, it must only be built for the specified platform.
- pub fn only_for_platform(&self) -> Option<&str> {
- self.inner.only_for_platform()
+ pub fn platform(&self) -> Option<&Platform> {
+ self.inner.platform()
}
/// Lock this dependency to depending on the specified package id
self.inner.matches_id(id)
}
}
+
+impl Platform {
+ pub fn matches(&self, name: &str, cfg: Option<&[Cfg]>) -> bool {
+ match *self {
+ Platform::Name(ref p) => p == name,
+ Platform::Cfg(ref p) => {
+ match cfg {
+ Some(cfg) => p.matches(cfg),
+ None => false,
+ }
+ }
+ }
+ }
+}
+
+impl Encodable for Platform {
+ fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
+ self.to_string().encode(s)
+ }
+}
+
+impl FromStr for Platform {
+ type Err = Box<CargoError>;
+
+ fn from_str(s: &str) -> CargoResult<Platform> {
+ if s.starts_with("cfg(") && s.ends_with(")") {
+ let s = &s[4..s.len()-1];
+ s.parse().map(Platform::Cfg).chain_error(|| {
+ human(format!("failed to parse `{}` as a cfg expression", s))
+ })
+ } else {
+ Ok(Platform::Name(s.to_string()))
+ }
+ }
+}
+
+impl fmt::Display for Platform {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ Platform::Name(ref n) => n.fmt(f),
+ Platform::Cfg(ref e) => write!(f, "cfg({})", e),
+ }
+ }
+}
use std::collections::{HashSet, HashMap};
use std::path::{Path, PathBuf};
-use std::str;
+use std::str::{self, FromStr};
use std::sync::Arc;
use regex::Regex;
use core::{SourceMap, Package, PackageId, PackageSet, Resolve, Target, Profile};
use core::{TargetKind, LibKind, Profiles, Metadata, Dependency};
use core::dependency::Kind as DepKind;
-use util::{self, CargoResult, ChainError, internal, Config, profile};
+use util::{self, CargoResult, ChainError, internal, Config, profile, Cfg, human};
use super::TargetConfig;
use super::custom_build::{BuildState, BuildScripts};
host: Layout,
target: Option<Layout>,
target_triple: String,
- host_dylib: Option<(String, String)>,
- host_staticlib: Option<(String, String)>,
- host_exe: String,
+ target_info: TargetInfo,
+ host_info: TargetInfo,
package_set: &'a PackageSet,
- target_dylib: Option<(String, String)>,
- target_staticlib: Option<(String, String)>,
- target_exe: String,
profiles: &'a Profiles,
}
+#[derive(Clone)]
+struct TargetInfo {
+ dylib: Option<(String, String)>,
+ staticlib: Option<(String, String)>,
+ exe: String,
+ cfg: Option<Vec<Cfg>>,
+}
+
impl<'a, 'cfg> Context<'a, 'cfg> {
pub fn new(resolve: &'a Resolve,
sources: &'a SourceMap<'cfg>,
profiles: &'a Profiles) -> CargoResult<Context<'a, 'cfg>> {
let target = build_config.requested_target.clone();
let target = target.as_ref().map(|s| &s[..]);
- let (target_dylib, target_staticlib, target_exe) = try!(Context::filename_parts(target,
- config));
- let (host_dylib, host_staticlib, host_exe) = if build_config.requested_target.is_none() {
- (target_dylib.clone(), target_staticlib.clone(), target_exe.clone())
+ let target_info = try!(Context::target_info(target, config));
+ let host_info = if build_config.requested_target.is_none() {
+ target_info.clone()
} else {
- try!(Context::filename_parts(None, config))
+ try!(Context::target_info(None, config))
};
let target_triple = target.unwrap_or_else(|| {
&config.rustc_info().host[..]
sources: sources,
package_set: deps,
config: config,
- target_dylib: target_dylib,
- target_staticlib: target_staticlib,
- target_exe: target_exe,
- host_dylib: host_dylib,
- host_staticlib: host_staticlib,
- host_exe: host_exe,
+ target_info: target_info,
+ host_info: host_info,
compilation: Compilation::new(config),
build_state: Arc::new(BuildState::new(&build_config, deps)),
build_config: build_config,
/// Run `rustc` to discover the dylib prefix/suffix for the target
/// specified as well as the exe suffix
- fn filename_parts(target: Option<&str>, cfg: &Config)
- -> CargoResult<(Option<(String, String)>, Option<(String, String)>, String)> {
+ fn target_info(target: Option<&str>, cfg: &Config)
+ -> CargoResult<TargetInfo> {
let mut process = util::process(cfg.rustc());
process.arg("-")
.arg("--crate-name").arg("_")
if let Some(s) = target {
process.arg("--target").arg(s);
};
- let output = try!(process.exec_with_output());
+
+ let mut with_cfg = process.clone();
+ with_cfg.arg("--print=cfg");
+
+ let mut has_cfg = true;
+ let output = try!(with_cfg.exec_with_output().or_else(|_| {
+ has_cfg = false;
+ process.exec_with_output()
+ }).chain_error(|| {
+ human(format!("failed to run `rustc` to learn about \
+ target-specific information"))
+ }));
let error = str::from_utf8(&output.stderr).unwrap();
let output = str::from_utf8(&output.stdout).unwrap();
Some((staticlib_parts[0].to_string(), staticlib_parts[1].to_string()))
};
- let exe_suffix = if nobin.is_match(error) {
+ let exe = if nobin.is_match(error) {
String::new()
} else {
lines.next().unwrap().trim()
.split('_').skip(1).next().unwrap().to_string()
};
- Ok((dylib, staticlib, exe_suffix))
+
+ let cfg = if has_cfg {
+ Some(try!(lines.map(Cfg::from_str).collect()))
+ } else {
+ None
+ };
+
+ Ok(TargetInfo {
+ dylib: dylib,
+ staticlib: staticlib,
+ exe: exe,
+ cfg: cfg,
+ })
}
/// Prepare this context, ensuring that all filesystem directories are in
/// otherwise it corresponds to the target platform.
fn dylib(&self, kind: Kind) -> CargoResult<(&str, &str)> {
let (triple, pair) = if kind == Kind::Host {
- (&self.config.rustc_info().host, &self.host_dylib)
+ (&self.config.rustc_info().host, &self.host_info.dylib)
} else {
- (&self.target_triple, &self.target_dylib)
+ (&self.target_triple, &self.target_info.dylib)
};
match *pair {
None => bail!("dylib outputs are not supported for {}", triple),
/// otherwise it corresponds to the target platform.
pub fn staticlib(&self, kind: Kind) -> CargoResult<(&str, &str)> {
let (triple, pair) = if kind == Kind::Host {
- (&self.config.rustc_info().host, &self.host_staticlib)
+ (&self.config.rustc_info().host, &self.host_info.staticlib)
} else {
- (&self.target_triple, &self.target_staticlib)
+ (&self.target_triple, &self.target_info.staticlib)
};
match *pair {
None => bail!("staticlib outputs are not supported for {}", triple),
pub fn target_filenames(&self, unit: &Unit) -> CargoResult<Vec<String>> {
let stem = self.file_stem(unit);
let suffix = if unit.target.for_host() {
- &self.host_exe
+ &self.host_info.exe
} else {
- &self.target_exe
+ &self.target_info.exe
};
let mut ret = Vec::new();
fn dep_platform_activated(&self, dep: &Dependency, kind: Kind) -> bool {
// If this dependency is only available for certain platforms,
// make sure we're only enabling it for that platform.
- match (dep.only_for_platform(), kind) {
- (Some(ref platform), Kind::Host) => {
- *platform == self.config.rustc_info().host
- },
- (Some(ref platform), Kind::Target) => {
- *platform == self.target_triple
- },
- (None, _) => true
- }
+ let platform = match dep.platform() {
+ Some(p) => p,
+ None => return true,
+ };
+ let (name, info) = match kind {
+ Kind::Host => (&self.config.rustc_info().host, &self.host_info),
+ Kind::Target => (&self.target_triple, &self.target_info),
+ };
+ platform.matches(name, info.cfg.as_ref().map(|cfg| &cfg[..]))
}
/// Gets a package for the given package id.
name: dep.name().to_string(),
features: dep.features().to_vec(),
version_req: dep.version_req().to_string(),
- target: dep.only_for_platform().map(|s| s.to_string()),
+ target: dep.platform().map(|s| s.to_string()),
kind: match dep.kind() {
Kind::Normal => "normal",
Kind::Build => "build",
_ => Kind::Normal,
};
+ let platform = match target {
+ Some(target) => Some(try!(target.parse())),
+ None => None,
+ };
+
// Unfortunately older versions of cargo and/or the registry ended up
// publishing lots of entries where the features array contained the
// empty feature, "", inside. This confuses the resolution process much
Ok(dep.set_optional(optional)
.set_default_features(default_features)
.set_features(features)
- .set_only_for_platform(target)
+ .set_platform(platform)
.set_kind(kind)
.into_dependency())
}
--- /dev/null
+use std::str::{self, FromStr};
+use std::iter;
+use std::fmt;
+
+use util::{CargoError, CargoResult, human};
+
+#[derive(Clone, PartialEq, Debug)]
+pub enum Cfg {
+ Name(String),
+ KeyPair(String, String),
+}
+
+#[derive(Clone, PartialEq, Debug)]
+pub enum CfgExpr {
+ Not(Box<CfgExpr>),
+ All(Vec<CfgExpr>),
+ Any(Vec<CfgExpr>),
+ Value(Cfg),
+}
+
+#[derive(PartialEq)]
+enum Token<'a> {
+ LeftParen,
+ RightParen,
+ Ident(&'a str),
+ Comma,
+ Equals,
+ String(&'a str),
+}
+
+struct Tokenizer<'a> {
+ s: iter::Peekable<str::CharIndices<'a>>,
+ orig: &'a str,
+}
+
+struct Parser<'a> {
+ t: iter::Peekable<Tokenizer<'a>>,
+}
+
+impl FromStr for Cfg {
+ type Err = Box<CargoError>;
+
+ fn from_str(s: &str) -> CargoResult<Cfg> {
+ let mut p = Parser::new(s);
+ let e = try!(p.cfg());
+ if p.t.next().is_some() {
+ bail!("malformed cfg value or key/value pair")
+ }
+ Ok(e)
+ }
+}
+
+impl fmt::Display for Cfg {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ Cfg::Name(ref s) => s.fmt(f),
+ Cfg::KeyPair(ref k, ref v) => write!(f, "{} = \"{}\"", k, v),
+ }
+ }
+}
+
+impl CfgExpr {
+ pub fn matches(&self, cfg: &[Cfg]) -> bool {
+ match *self {
+ CfgExpr::Not(ref e) => !e.matches(cfg),
+ CfgExpr::All(ref e) => e.iter().all(|e| e.matches(cfg)),
+ CfgExpr::Any(ref e) => e.iter().any(|e| e.matches(cfg)),
+ CfgExpr::Value(ref e) => cfg.contains(e),
+ }
+ }
+}
+
+impl FromStr for CfgExpr {
+ type Err = Box<CargoError>;
+
+ fn from_str(s: &str) -> CargoResult<CfgExpr> {
+ let mut p = Parser::new(s);
+ let e = try!(p.expr());
+ if p.t.next().is_some() {
+ bail!("can only have one cfg-expression, consider using all() or \
+ any() explicitly")
+ }
+ Ok(e)
+ }
+}
+
+impl fmt::Display for CfgExpr {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ CfgExpr::Not(ref e) => write!(f, "not({})", e),
+ CfgExpr::All(ref e) => write!(f, "all({})", CommaSep(e)),
+ CfgExpr::Any(ref e) => write!(f, "any({})", CommaSep(e)),
+ CfgExpr::Value(ref e) => write!(f, "{}", e),
+ }
+ }
+}
+
+struct CommaSep<'a, T: 'a>(&'a [T]);
+
+impl<'a, T: fmt::Display> fmt::Display for CommaSep<'a, T> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ for (i, v) in self.0.iter().enumerate() {
+ if i > 0 {
+ try!(write!(f, ", "));
+ }
+ try!(write!(f, "{}", v));
+ }
+ Ok(())
+ }
+}
+
+impl<'a> Parser<'a> {
+ fn new(s: &'a str) -> Parser<'a> {
+ Parser {
+ t: Tokenizer {
+ s: s.char_indices().peekable(),
+ orig: s,
+ }.peekable(),
+ }
+ }
+
+ fn expr(&mut self) -> CargoResult<CfgExpr> {
+ match self.t.peek() {
+ Some(&Ok(Token::Ident(op @ "all"))) |
+ Some(&Ok(Token::Ident(op @ "any"))) => {
+ self.t.next();
+ let mut e = Vec::new();
+ try!(self.eat(Token::LeftParen));
+ while !self.try(Token::RightParen) {
+ e.push(try!(self.expr()));
+ if !self.try(Token::Comma) {
+ try!(self.eat(Token::RightParen));
+ break
+ }
+ }
+ if op == "all" {
+ Ok(CfgExpr::All(e))
+ } else {
+ Ok(CfgExpr::Any(e))
+ }
+ }
+ Some(&Ok(Token::Ident("not"))) => {
+ self.t.next();
+ try!(self.eat(Token::LeftParen));
+ let e = try!(self.expr());
+ try!(self.eat(Token::RightParen));
+ Ok(CfgExpr::Not(Box::new(e)))
+ }
+ Some(&Ok(..)) => self.cfg().map(CfgExpr::Value),
+ Some(&Err(..)) => {
+ Err(self.t.next().unwrap().err().unwrap())
+ }
+ None => bail!("expected start of a cfg expression, \
+ found nothing"),
+ }
+ }
+
+ fn cfg(&mut self) -> CargoResult<Cfg> {
+ match self.t.next() {
+ Some(Ok(Token::Ident(name))) => {
+ let e = if self.try(Token::Equals) {
+ let val = match self.t.next() {
+ Some(Ok(Token::String(s))) => s,
+ Some(Ok(t)) => bail!("expected a string, found {}",
+ t.classify()),
+ Some(Err(e)) => return Err(e),
+ None => bail!("expected a string, found nothing"),
+ };
+ Cfg::KeyPair(name.to_string(), val.to_string())
+ } else {
+ Cfg::Name(name.to_string())
+ };
+ Ok(e)
+ }
+ Some(Ok(t)) => bail!("expected identifier, found {}", t.classify()),
+ Some(Err(e)) => Err(e),
+ None => bail!("expected identifier, found nothing"),
+ }
+ }
+
+ fn try(&mut self, token: Token<'a>) -> bool {
+ match self.t.peek() {
+ Some(&Ok(ref t)) if token == *t => {}
+ _ => return false,
+ }
+ self.t.next();
+ true
+ }
+
+ fn eat(&mut self, token: Token<'a>) -> CargoResult<()> {
+ match self.t.next() {
+ Some(Ok(ref t)) if token == *t => Ok(()),
+ Some(Ok(t)) => bail!("expected {}, found {}", token.classify(),
+ t.classify()),
+ Some(Err(e)) => Err(e),
+ None => bail!("expected {}, but cfg expr ended", token.classify()),
+ }
+ }
+}
+
+impl<'a> Iterator for Tokenizer<'a> {
+ type Item = CargoResult<Token<'a>>;
+
+ fn next(&mut self) -> Option<CargoResult<Token<'a>>> {
+ loop {
+ match self.s.next() {
+ Some((_, ' ')) => {}
+ Some((_, '(')) => return Some(Ok(Token::LeftParen)),
+ Some((_, ')')) => return Some(Ok(Token::RightParen)),
+ Some((_, ',')) => return Some(Ok(Token::Comma)),
+ Some((_, '=')) => return Some(Ok(Token::Equals)),
+ Some((start, '"')) => {
+ while let Some((end, ch)) = self.s.next() {
+ if ch == '"' {
+ return Some(Ok(Token::String(&self.orig[start+1..end])))
+ }
+ }
+ return Some(Err(human(format!("unterminated string in cfg"))))
+ }
+ Some((start, ch)) if is_ident_start(ch) => {
+ while let Some(&(end, ch)) = self.s.peek() {
+ if !is_ident_rest(ch) {
+ return Some(Ok(Token::Ident(&self.orig[start..end])))
+ } else {
+ self.s.next();
+ }
+ }
+ return Some(Ok(Token::Ident(&self.orig[start..])))
+ }
+ Some((_, ch)) => {
+ return Some(Err(human(format!("unexpected character in \
+ cfg `{}`, expected parens, \
+ a comma, an identifier, or \
+ a string", ch))))
+ }
+ None => return None
+ }
+ }
+ }
+}
+
+fn is_ident_start(ch: char) -> bool {
+ ch == '_' || ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z')
+}
+
+fn is_ident_rest(ch: char) -> bool {
+ is_ident_start(ch) || ('0' <= ch && ch <= '9')
+}
+
+impl<'a> Token<'a> {
+ fn classify(&self) -> &str {
+ match *self {
+ Token::LeftParen => "`(`",
+ Token::RightParen => "`)`",
+ Token::Ident(..) => "an identifier",
+ Token::Comma => "`,`",
+ Token::Equals => "`=`",
+ Token::String(..) => "a string",
+ }
+ }
+}
+pub use self::cfg::{Cfg, CfgExpr};
pub use self::config::Config;
pub use self::dependency_queue::Dependency;
pub use self::dependency_queue::{DependencyQueue, Fresh, Dirty, Freshness};
pub mod toml;
pub mod lev_distance;
pub mod job;
+mod cfg;
mod dependency_queue;
+mod rustc;
mod sha256;
mod shell_escape;
mod vcs;
-mod rustc;
use core::{SourceId, Profiles};
use core::{Summary, Manifest, Target, Dependency, DependencyInner, PackageId,
GitReference};
-use core::dependency::Kind;
+use core::dependency::{Kind, Platform};
use core::manifest::{LibKind, Profile, ManifestMetadata};
use core::package_id::Metadata;
use util::{self, CargoResult, human, ToUrl, ToSemver, ChainError, Config};
source_id: &'a SourceId,
nested_paths: &'a mut Vec<PathBuf>,
config: &'b Config,
+ warnings: &'a mut Vec<String>,
+ platform: Option<Platform>,
}
// These functions produce the equivalent of specific manifest entries. One
source_id: source_id,
nested_paths: &mut nested_paths,
config: config,
+ warnings: &mut warnings,
+ platform: None,
};
// Collect the deps
try!(process_dependencies(&mut cx, self.dependencies.as_ref(),
- |dep| dep, &mut warnings));
+ None));
try!(process_dependencies(&mut cx, self.dev_dependencies.as_ref(),
- |dep| dep.set_kind(Kind::Development), &mut warnings));
+ Some(Kind::Development)));
try!(process_dependencies(&mut cx, self.build_dependencies.as_ref(),
- |dep| dep.set_kind(Kind::Build), &mut warnings));
+ Some(Kind::Build)));
if let Some(targets) = self.target.as_ref() {
for (name, platform) in targets.iter() {
+ cx.platform = Some(try!(name.parse()));
try!(process_dependencies(&mut cx,
platform.dependencies.as_ref(),
- |dep| {
- dep.set_only_for_platform(Some(name.clone()))
- }, &mut warnings));
+ None));
try!(process_dependencies(&mut cx,
platform.build_dependencies.as_ref(),
- |dep| {
- dep.set_only_for_platform(Some(name.clone()))
- .set_kind(Kind::Build)
- }, &mut warnings));
+ Some(Kind::Build)));
try!(process_dependencies(&mut cx,
platform.dev_dependencies.as_ref(),
- |dep| {
- dep.set_only_for_platform(Some(name.clone()))
- .set_kind(Kind::Development)
- }, &mut warnings));
+ Some(Kind::Development)));
}
}
}
}
}
-fn process_dependencies<F>(cx: &mut Context,
- new_deps: Option<&HashMap<String, TomlDependency>>,
- mut f: F,
- warnings: &mut Vec<String>) -> CargoResult<()>
- where F: FnMut(DependencyInner) -> DependencyInner
-{
+fn process_dependencies(cx: &mut Context,
+ new_deps: Option<&HashMap<String, TomlDependency>>,
+ kind: Option<Kind>)
+ -> CargoResult<()> {
let dependencies = match new_deps {
Some(ref dependencies) => dependencies,
None => return Ok(())
TomlDependency::Detailed(ref details) => details.clone(),
};
- if details.version.is_none() && details.path.is_none() && details.git.is_none() {
- warnings.push(format!("warning: dependency ({}) specified without providing a local \
- path, Git repository, or version to use. This will be \
- considered an error in future versions", n));
+ if details.version.is_none() && details.path.is_none() &&
+ details.git.is_none() {
+ cx.warnings.push(format!("warning: dependency ({}) specified \
+ without providing a local path, Git \
+ repository, or version to use. This will \
+ be considered an error in future \
+ versions", n));
}
let reference = details.branch.clone().map(GitReference::Branch)
}
}.unwrap_or(try!(SourceId::for_central(cx.config)));
- let dep = try!(DependencyInner::parse(&n,
- details.version.as_ref()
- .map(|v| &v[..]),
- &new_source_id));
- let dep = f(dep)
- .set_features(details.features.unwrap_or(Vec::new()))
- .set_default_features(details.default_features.unwrap_or(true))
- .set_optional(details.optional.unwrap_or(false))
- .into_dependency();
- cx.deps.push(dep);
+ let version = details.version.as_ref().map(|v| &v[..]);
+ let mut dep = try!(DependencyInner::parse(&n, version, &new_source_id));
+ dep = dep.set_features(details.features.unwrap_or(Vec::new()))
+ .set_default_features(details.default_features.unwrap_or(true))
+ .set_optional(details.optional.unwrap_or(false))
+ .set_platform(cx.platform.clone());
+ if let Some(kind) = kind {
+ dep = dep.set_kind(kind);
+ }
+ cx.deps.push(dep.into_dependency());
}
Ok(())
guide](crates-io.html#using-cratesio-based-crates).
Platform-specific dependencies take the same format, but are listed under the
-`target.$triple` section:
+`target` section. Normally Rust-like `#[cfg]` syntax will be used to define
+these sections:
```toml
-[target.x86_64-pc-windows-gnu.dependencies]
+[target.'cfg(windows)'.dependencies]
winhttp = "0.4.0"
-[target.i686-unknown-linux-gnu.dependencies]
+[target.'cfg(unix)'.dependencies]
openssl = "1.0.1"
+
+[target.'cfg(target_pointer_width = "32")'.dependencies]
native = { path = "native/i686" }
-[target.x86_64-unknown-linux-gnu.dependencies]
+[target.'cfg(target_pointer_width = "64")'.dependencies]
+native = { path = "native/i686" }
+```
+
+Like with Rust, the syntax here supports the `not`, `any`, and `all` operators
+to combine various cfg name/value pairs. Note that the `cfg` syntax has only
+been available since Cargo 0.9.0 (Rust 1.8.0).
+
+In addition to `#[cfg]` syntax, Cargo also supports listing out the full target
+the dependencies would apply to:
+
+```toml
+[target.x86_64-pc-windows-gnu.dependencies]
+winhttp = "0.4.0"
+
+[target.i686-unknown-linux-gnu.dependencies]
openssl = "1.0.1"
-native = { path = "native/x86_64" }
```
If you’re using a custom target specification, quote the full path and file
pub struct Package {
name: String,
vers: String,
- deps: Vec<(String, String, &'static str)>,
+ deps: Vec<(String, String, &'static str, String)>,
files: Vec<(String, String)>,
yanked: bool,
}
}
pub fn dep(&mut self, name: &str, vers: &str) -> &mut Package {
- self.deps.push((name.to_string(), vers.to_string(), "normal"));
+ self.deps.push((name.to_string(), vers.to_string(), "normal",
+ "null".to_string()));
+ self
+ }
+
+ pub fn target_dep(&mut self,
+ name: &str,
+ vers: &str,
+ target: &str) -> &mut Package {
+ self.deps.push((name.to_string(), vers.to_string(), "normal",
+ format!("\"{}\"", target)));
self
}
pub fn dev_dep(&mut self, name: &str, vers: &str) -> &mut Package {
- self.deps.push((name.to_string(), vers.to_string(), "dev"));
+ self.deps.push((name.to_string(), vers.to_string(), "dev",
+ "null".to_string()));
self
}
self.make_archive();
// Figure out what we're going to write into the index
- let deps = self.deps.iter().map(|&(ref name, ref req, ref kind)| {
+ let deps = self.deps.iter().map(|&(ref name, ref req, ref kind, ref target)| {
format!("{{\"name\":\"{}\",\
\"req\":\"{}\",\
\"features\":[],\
\"default_features\":false,\
- \"target\":null,\
+ \"target\":{},\
\"optional\":false,\
- \"kind\":\"{}\"}}", name, req, kind)
+ \"kind\":\"{}\"}}", name, req, target, kind)
}).collect::<Vec<_>>().connect(",");
let cksum = {
let mut c = Vec::new();
version = "{}"
authors = []
"#, self.name, self.vers);
- for &(ref dep, ref req, kind) in self.deps.iter() {
- manifest.push_str(&format!(r#"
- [{}dependencies.{}]
- version = "{}"
- "#, match kind {
+ for &(ref dep, ref req, kind, ref target) in self.deps.iter() {
+ let target = match &target[..] {
+ "null" => String::new(),
+ t => format!("target.{}.", t),
+ };
+ let kind = match kind {
"build" => "build-",
"dev" => "dev-",
_ => ""
- }, dep, req));
+ };
+ manifest.push_str(&format!(r#"
+ [{}{}dependencies.{}]
+ version = "{}"
+ "#, target, kind, dep, req));
}
let dst = self.archive_dst();
--- /dev/null
+use std::str::FromStr;
+use std::fmt;
+
+use cargo::util::{Cfg, CfgExpr};
+use hamcrest::assert_that;
+
+use support::{project, execs, COMPILING, UPDATING, DOWNLOADING};
+use support::registry::Package;
+
+macro_rules! c {
+ ($a:ident) => (
+ Cfg::Name(stringify!($a).to_string())
+ );
+ ($a:ident = $e:expr) => (
+ Cfg::KeyPair(stringify!($a).to_string(), $e.to_string())
+ );
+}
+
+macro_rules! e {
+ (any($($t:tt),*)) => (CfgExpr::Any(vec![$(e!($t)),*]));
+ (all($($t:tt),*)) => (CfgExpr::All(vec![$(e!($t)),*]));
+ (not($($t:tt)*)) => (CfgExpr::Not(Box::new(e!($($t)*))));
+ (($($t:tt)*)) => (e!($($t)*));
+ ($($t:tt)*) => (CfgExpr::Value(c!($($t)*)));
+}
+
+fn good<T>(s: &str, expected: T)
+ where T: FromStr + PartialEq + fmt::Debug,
+ T::Err: fmt::Display
+{
+ let c = match T::from_str(s) {
+ Ok(c) => c,
+ Err(e) => panic!("failed to parse `{}`: {}", s, e),
+ };
+ assert_eq!(c, expected);
+}
+
+fn bad<T>(s: &str, err: &str)
+ where T: FromStr + fmt::Display, T::Err: fmt::Display
+{
+ let e = match T::from_str(s) {
+ Ok(cfg) => panic!("expected `{}` to not parse but got {}", s, cfg),
+ Err(e) => e.to_string(),
+ };
+ assert!(e.contains(err), "when parsing `{}`,\n\"{}\" not contained \
+ inside: {}", s, err, e);
+}
+
+#[test]
+fn cfg_syntax() {
+ good("foo", c!(foo));
+ good("_bar", c!(_bar));
+ good(" foo", c!(foo));
+ good(" foo ", c!(foo));
+ good(" foo = \"bar\"", c!(foo = "bar"));
+ good("foo=\"\"", c!(foo = ""));
+ good(" foo=\"3\" ", c!(foo = "3"));
+ good("foo = \"3 e\"", c!(foo = "3 e"));
+}
+
+#[test]
+fn cfg_syntax_bad() {
+ bad::<Cfg>("", "found nothing");
+ bad::<Cfg>(" ", "found nothing");
+ bad::<Cfg>("\t", "unexpected character");
+ bad::<Cfg>("7", "unexpected character");
+ bad::<Cfg>("=", "expected identifier");
+ bad::<Cfg>(",", "expected identifier");
+ bad::<Cfg>("(", "expected identifier");
+ bad::<Cfg>("foo (", "malformed cfg value");
+ bad::<Cfg>("bar =", "expected a string");
+ bad::<Cfg>("bar = \"", "unterminated string");
+ bad::<Cfg>("foo, bar", "malformed cfg value");
+}
+
+#[test]
+fn cfg_expr() {
+ good("foo", e!(foo));
+ good("_bar", e!(_bar));
+ good(" foo", e!(foo));
+ good(" foo ", e!(foo));
+ good(" foo = \"bar\"", e!(foo = "bar"));
+ good("foo=\"\"", e!(foo = ""));
+ good(" foo=\"3\" ", e!(foo = "3"));
+ good("foo = \"3 e\"", e!(foo = "3 e"));
+
+ good("all()", e!(all()));
+ good("all(a)", e!(all(a)));
+ good("all(a, b)", e!(all(a, b)));
+ good("all(a, )", e!(all(a)));
+ good("not(a = \"b\")", e!(not(a = "b")));
+ good("not(all(a))", e!(not(all(a))));
+}
+
+#[test]
+fn cfg_expr_bad() {
+ bad::<CfgExpr>(" ", "found nothing");
+ bad::<CfgExpr>(" all", "expected `(`");
+ bad::<CfgExpr>("all(a", "expected `)`");
+ bad::<CfgExpr>("not", "expected `(`");
+ bad::<CfgExpr>("not(a", "expected `)`");
+ bad::<CfgExpr>("a = ", "expected a string");
+ bad::<CfgExpr>("all(not())", "expected identifier");
+ bad::<CfgExpr>("foo(a)", "consider using all() or any() explicitly");
+}
+
+#[test]
+fn cfg_matches() {
+ assert!(e!(foo).matches(&[c!(bar), c!(foo), c!(baz)]));
+ assert!(e!(any(foo)).matches(&[c!(bar), c!(foo), c!(baz)]));
+ assert!(e!(any(foo, bar)).matches(&[c!(bar)]));
+ assert!(e!(any(foo, bar)).matches(&[c!(foo)]));
+ assert!(e!(all(foo, bar)).matches(&[c!(foo), c!(bar)]));
+ assert!(e!(all(foo, bar)).matches(&[c!(foo), c!(bar)]));
+ assert!(e!(not(foo)).matches(&[c!(bar)]));
+ assert!(e!(not(foo)).matches(&[]));
+ assert!(e!(any((not(foo)), (all(foo, bar)))).matches(&[c!(bar)]));
+ assert!(e!(any((not(foo)), (all(foo, bar)))).matches(&[c!(foo), c!(bar)]));
+
+ assert!(!e!(foo).matches(&[]));
+ assert!(!e!(foo).matches(&[c!(bar)]));
+ assert!(!e!(foo).matches(&[c!(fo)]));
+ assert!(!e!(any(foo)).matches(&[]));
+ assert!(!e!(any(foo)).matches(&[c!(bar)]));
+ assert!(!e!(any(foo)).matches(&[c!(bar), c!(baz)]));
+ assert!(!e!(all(foo)).matches(&[c!(bar), c!(baz)]));
+ assert!(!e!(all(foo, bar)).matches(&[c!(bar)]));
+ assert!(!e!(all(foo, bar)).matches(&[c!(foo)]));
+ assert!(!e!(all(foo, bar)).matches(&[]));
+ assert!(!e!(not(bar)).matches(&[c!(bar)]));
+ assert!(!e!(not(bar)).matches(&[c!(baz), c!(bar)]));
+ assert!(!e!(any((not(foo)), (all(foo, bar)))).matches(&[c!(foo)]));
+}
+
+fn setup() {}
+
+test!(cfg_easy {
+ if !::is_nightly() { return }
+
+ let p = project("foo")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+
+ [target.'cfg(unix)'.dependencies]
+ b = { path = 'b' }
+ [target."cfg(windows)".dependencies]
+ b = { path = 'b' }
+ "#)
+ .file("src/lib.rs", "extern crate b;")
+ .file("b/Cargo.toml", r#"
+ [package]
+ name = "b"
+ version = "0.0.1"
+ authors = []
+ "#)
+ .file("b/src/lib.rs", "");
+ assert_that(p.cargo_process("build").arg("-v"),
+ execs().with_status(0));
+});
+
+test!(dont_include {
+ if !::is_nightly() { return }
+
+ let other_family = if cfg!(unix) {"windows"} else {"unix"};
+ let p = project("foo")
+ .file("Cargo.toml", &format!(r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+
+ [target.'cfg({})'.dependencies]
+ b = {{ path = 'b' }}
+ "#, other_family))
+ .file("src/lib.rs", "")
+ .file("b/Cargo.toml", r#"
+ [package]
+ name = "b"
+ version = "0.0.1"
+ authors = []
+ "#)
+ .file("b/src/lib.rs", "");
+ assert_that(p.cargo_process("build"),
+ execs().with_status(0).with_stdout(&format!("\
+{compiling} a v0.0.1 ([..])
+", compiling = COMPILING)));
+});
+
+test!(works_through_the_registry {
+ if !::is_nightly() { return }
+
+ Package::new("foo", "0.1.0").publish();
+ Package::new("bar", "0.1.0")
+ .target_dep("foo", "0.1.0", "cfg(unix)")
+ .target_dep("foo", "0.1.0", "cfg(windows)")
+ .publish();
+
+ let p = project("a")
+ .file("Cargo.toml", &r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+ "#)
+ .file("src/lib.rs", "extern crate bar;");
+
+ assert_that(p.cargo_process("build"),
+ execs().with_status(0).with_stdout(&format!("\
+{updating} registry [..]
+{downloading} [..]
+{downloading} [..]
+{compiling} foo v0.1.0 ([..])
+{compiling} bar v0.1.0 ([..])
+{compiling} a v0.0.1 ([..])
+", compiling = COMPILING, updating = UPDATING, downloading = DOWNLOADING)));
+});
+
+test!(bad_target_spec {
+ let p = project("a")
+ .file("Cargo.toml", &r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+
+ [target.'cfg(4)'.dependencies]
+ bar = "0.1.0"
+ "#)
+ .file("src/lib.rs", "");
+
+ assert_that(p.cargo_process("build"),
+ execs().with_status(101).with_stderr("\
+failed to parse manifest at `[..]`
+
+Caused by:
+ failed to parse `4` as a cfg expression
+
+Caused by:
+ unexpected character in cfg `4`, [..]
+"));
+});
+
+test!(bad_target_spec2 {
+ let p = project("a")
+ .file("Cargo.toml", &r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+
+ [target.'cfg(foo =)'.dependencies]
+ bar = "0.1.0"
+ "#)
+ .file("src/lib.rs", "");
+
+ assert_that(p.cargo_process("build"),
+ execs().with_status(101).with_stderr("\
+failed to parse manifest at `[..]`
+
+Caused by:
+ failed to parse `foo =` as a cfg expression
+
+Caused by:
+ expected a string, found nothing
+"));
+});
+
+test!(multiple_match_ok {
+ if !::is_nightly() { return }
+
+ let p = project("foo")
+ .file("Cargo.toml", &format!(r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+
+ [target.'cfg(unix)'.dependencies]
+ b = {{ path = 'b' }}
+ [target.'cfg(target_family = "unix")'.dependencies]
+ b = {{ path = 'b' }}
+ [target."cfg(windows)".dependencies]
+ b = {{ path = 'b' }}
+ [target.'cfg(target_family = "windows")'.dependencies]
+ b = {{ path = 'b' }}
+ [target."cfg(any(windows, unix))".dependencies]
+ b = {{ path = 'b' }}
+
+ [target.{}.dependencies]
+ b = {{ path = 'b' }}
+ "#, ::rustc_host()))
+ .file("src/lib.rs", "extern crate b;")
+ .file("b/Cargo.toml", r#"
+ [package]
+ name = "b"
+ version = "0.0.1"
+ authors = []
+ "#)
+ .file("b/src/lib.rs", "");
+ assert_that(p.cargo_process("build").arg("-v"),
+ execs().with_status(0));
+});
+
+test!(any_ok {
+ if !::is_nightly() { return }
+
+ let p = project("foo")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+
+ [target."cfg(any(windows, unix))".dependencies]
+ b = { path = 'b' }
+ "#)
+ .file("src/lib.rs", "extern crate b;")
+ .file("b/Cargo.toml", r#"
+ [package]
+ name = "b"
+ version = "0.0.1"
+ authors = []
+ "#)
+ .file("b/src/lib.rs", "");
+ assert_that(p.cargo_process("build").arg("-v"),
+ execs().with_status(0));
+});
mod test_cargo_version;
mod test_shell;
mod test_cargo_death;
+mod test_cargo_cfg;
thread_local!(static RUSTC: Rustc = Rustc::new("rustc").unwrap());